﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace XiphMediaEncoder
{  
   public static class TagProcessor
   {
      private static bool IsCorrectTag(string tag)
      {
         if (tag.Trim().Length == 0)
            return false;

         return tag.All(c => c >= 30 && c <= 125 && c != '=');
      }

      public static string FileNameFromTags(string template, SortedList<string, List<string>> tags)
      {
         string result = PatternFromTags(template, tags, s => s).ToString();
         string filename = PathNormalizer.NormalizeFilename(result);

         if (filename == null || filename.Trim().Length == 0)
            return null;

         return filename;
      }

      private static bool IsTemplateComplete(string sub, SortedList<string, List<string>> tags, Regex rgxTag)
      {
         MatchCollection mc = rgxTag.Matches(sub);
         var ls = new List<string>();
         ls.AddRange(from Match m in mc select m.Value);

         foreach (string s in ls)
         {
            mc = rgxTag.Matches(s);
            foreach (Match m in mc)
            {
               string field = m.Value.Substring(1, m.Value.Length - 2).ToUpperInvariant();
               if (!tags.ContainsKey(field))
                  return false;
            }
         }

         return true;
      }

      public static string PathFromTags(string template, SortedList<string, List<string>> tags)
      {
         StringBuilder buffer = PatternFromTags(template, tags, s => s.Replace("\\", null));
         if (buffer.Length > 2)
            buffer.Replace(@"\\", @"\", 2, buffer.Length - 2);

         return PathNormalizer.NormalizePath(buffer.ToString());
      }

      private static StringBuilder PatternFromTags(string template, SortedList<string, List<string>> tags, Func<string, string> tagValueNormalizer)
      {
         var buffer = new StringBuilder(template);
         var rgxTag = new Regex(@"\|.*?\|");

         while (true)
         {
            string s = buffer.ToString();
            int begin = s.LastIndexOf('<');
            if (begin == -1) break;

            int end = s.IndexOf('>', begin);
            int length = end - begin + 1;
            string sub = s.Substring(begin, length);
            if (IsTemplateComplete(sub, tags, rgxTag))
            {
               buffer.Remove(end, 1);
               buffer.Remove(begin, 1);
            }
            else
               buffer.Remove(begin, length);
         }

         MatchCollection mc = rgxTag.Matches(buffer.ToString());
         var ls = new List<string>();
         ls.AddRange(from Match m in mc select m.Value);

         foreach (string s in ls)
         {
            var buffer2 = new StringBuilder(s);
            mc = rgxTag.Matches(s);
            bool clear = false;
            foreach (Match m in mc)
            {
               string field = m.Value.Substring(1, m.Value.Length - 2).ToUpperInvariant();
               if (tags.ContainsKey(field))
                  buffer2.Replace(m.Value, tagValueNormalizer(tags[field][0]));
               else
                  clear = true;
            }

            buffer.Replace(s, clear ? null : buffer2.ToString());
         }

         return buffer;
      }      

      public static SortedList<string, List<string>> TagFromFilename(SortedList<string, List<string>> tags, string template, string filename)
      {
         if (tags == null)
            tags = new SortedList<string, List<string>>();

         if (String.IsNullOrEmpty(template))
            return tags;

         var rgxTag = new Regex(@"(\|.*?\|)");
         string[] tokens = rgxTag.Split(template);
         filename = Path.GetFileNameWithoutExtension(filename);

         TagFromPattern(rgxTag, tokens, filename, tags);
         return tags;
      }

      public static SortedList<string, List<string>> TagFromPath(SortedList<string, List<string>> tags, string template, string path)
      {
         if (tags == null)
            tags = new SortedList<string, List<string>>();

         if (String.IsNullOrEmpty(template))
            return tags;

         var rgxTag = new Regex(@"(\|.*?\|)");
         var sep = new[] { '\\' };

         string[] sections = template.Split(sep);
         string[] folders = !path.StartsWith(@"\\") ?
            path.Remove(0, 3).Split(sep) :
            path.Remove(0, path.IndexOf(sep.ToString(), 3) + 2).Split(sep);

         Array.Reverse(sections);
         Array.Reverse(folders);

         for (int i = 0; i < sections.Length; i++)
         {
            if (i >= folders.Length) break;
            string[] tokens = rgxTag.Split(sections[i]);

            TagFromPattern(rgxTag, tokens, folders[i], tags);
         }

         return tags;
      }

      private static void TagFromPattern(Regex rgxTag, string[] tokens, string pathItem, SortedList<string, List<string>> tags)
      {
         for (int i = 0; i < tokens.Length; i++)
         {
            if (rgxTag.IsMatch(tokens[i]))
            {
               string next = tokens[i + 1];

               int index;
               if (next.Length == 0)
               {
                  index = pathItem.Length;
               }
               else
               {
                  index = pathItem.IndexOf(next);
                  if (index == -1) break;
               }

               string tag = pathItem.Substring(0, index);
               string field = tokens[i].Substring(1, tokens[i].Length - 2).ToUpperInvariant();
               if (field != "FAKE" && IsCorrectTag(field) && tag.Length != 0)
               {
                  if (!tags.ContainsKey(field))
                     tags.Add(field, new List<string>(new[] { tag }));
                  else
                     tags[field].Add(tag);
               }

               pathItem = pathItem.Remove(0, tag.Length);
            }
            else
            {
               if (tokens[i].Length <= pathItem.Length)
                  pathItem = pathItem.Remove(0, tokens[i].Length);
               else
                  break;
            }
         }
      }
   }
}